import {
  inject,
  getCurrentInstance,
  effectScope,
  reactive,
  computed,
  toRefs,
  isRef,
  watch,
} from "vue";
import { symbolPinia, setActivePinia, activePinia } from "./rootStore";
import { addSubscriber, publish } from "./PubSub";
const isObject = (val) => typeof val === "object" && val !== null;
function mergeReactive(target, source) {
  for (const key in source) {
    // 只合并 source 中有的属性
    if (!source.hasOwnProperty(key)) {
      continue;
    }
    const oldValue = target[key];
    const newValue = source[key];
    // 状态有可能是ref
    if (isObject(oldValue) && isObject(newValue) && !isRef(newValue)) {
      mergeReactive(oldValue, newValue);
    } else {
      target[key] = newValue;
    }
  }
}
export function defineStore(idOrOptions, setup) {
  let id, options;
  if (typeof idOrOptions === "string") {
    id = idOrOptions;
    options = setup;
  } else {
    id = idOrOptions.id;
    options = idOrOptions;
  }
  const isSetupStore = typeof setup === "function";
  // 定义 useStore 函数
  function useStore() {
    let instance = getCurrentInstance();

    let pinia = instance && inject(symbolPinia);
    if (pinia) {
      setActivePinia(pinia);
    }
    pinia = activePinia;
    // 如果 pinia 中没有 id 对应的 store，就创建一个
    if (!pinia._s.has(id)) {
      if (isSetupStore) {
        createSetupStore(id, setup, pinia);
      } else {
        createOptionsStore(id, options, pinia);
      }
    }
    // 从 pinia 中获取 id 对应的 store
    const store = pinia._s.get(id);
    return store;
  }

  return useStore;
}
/**
 * 创建一个 setup store
 * @param {string} id - store 的 id
 * @param {object} options - store 的选项
 * @param {object} pinia - pinia 实例
 */
function createSetupStore(id, setup, pinia) {
  let scope = null;
  const stopStore = pinia._e.run(() => {
    scope = effectScope();
    return scope.run(() => setup());
  });
  const wrapAction = (name, action) => {
    return function (...args) {
      const afterCallbacks = [];
      const errorCallbacks = [];

      function after(callback) {
        afterCallbacks.push(callback);
      }
      function onError(callback) {
        errorCallbacks.push(callback);
      }
      // ✅ 第 3 步：发布（publish）
      publish(actionSubscribers, { after, onError, store, name });
      // 触发 action 时，将 store 作为 this 指向
      // 并且可以直接store.xxx()
      let result;
      try {
        result = action.apply(store, args);
      } catch (error) {
        publish(errorCallbacks, error);
      }
      if (result instanceof Promise) {
        return result
          .then((res) => {
            publish(afterCallbacks, res);
            return res;
          })
          .catch((err) => {
            publish(errorCallbacks, err);
            return Promise.reject(err);
          });
      } else {
        publish(afterCallbacks, result);
      }
      return result;
    };
  };
  for (const key in stopStore) {
    const prop = stopStore[key];
    // 只绑定函数 改变 this 指向
    if (typeof prop === "function") {
      stopStore[key] = wrapAction(key, prop);
    }
  }

  function $patch(partialStateOrMutation) {
    if (typeof partialStateOrMutation === "function") {
      partialStateOrMutation(store);
    } else {
      mergeReactive(store, partialStateOrMutation);
    }
  }
  let actionSubscribers = [];
  const partialStore = {
    $patch,
    // watch 状态变化 发布订阅模式
    $subscribe(callback, options) {
      scope.run(
        () =>
          watch(pinia.state.value[id], (state) => {
            callback({ type: "patch" }, state);
          }),
        options
      );
    },
    // ✅ 第 1 步：订阅（subscribe）
    // bind方法可以分多次传参，最后函数运行时会把所有参数连接起来一起放入函数运行。
    $onAction: addSubscriber.bind(null, actionSubscribers),
    $dispose: () => {
      //释放 effectScope
      scope.stop();
      pinia._s.delete(id); //删除 store
      actionSubscribers = []; //删除订阅者
    },
  };
  const store = reactive(partialStore); //每一个 store 都是一个 reactive 对象
  Object.defineProperty(store, "$state", {
    // 只要状态
    get() {
      return pinia.state.value[id];
    },
    set(state) {
      // 完全替换状态
      $patch(($state) => Object.assign($state, state));
    },
  });

  // 最终会把 stopStore 放到 store 中
  Object.assign(store, stopStore); //reactive 中放ref会自动 拆包 store.count.value
  // 插件数组
  pinia._p.forEach((plugin) => {
    Object.assign(store, plugin({ store, pinia, app: pinia._a }));
  });
  pinia._s.set(id, store);
  return store;
}

/**
 * 创建一个 options store
 * @param {string} id - store 的 id
 * @param {object} options - store 的选项
 * @param {object} pinia - pinia 实例
 */
function createOptionsStore(id, options, pinia) {
  let { state, getters, actions } = options;

  function setup() {
    //   ref 放入的是对象，会自动proxy
    pinia.state.value[id] = state ? state() : {};
    // 执行 setup 函数，将 state 放入 pinia.state.value[id] 中
    const localState = toRefs(pinia.state.value[id]);
    let computedGetters = Object.keys(getters || {}).reduce((obj, key) => {
      // 此处 执行的返回结果不是一个 ref，而是一个普通的值 需要用 toRefs 转换为 ref
      obj[key] = computed(() => getters[key].call(store, store));
      return obj;
    }, {});
    return Object.assign(localState, actions, computedGetters);
  }
  const store = createSetupStore(id, setup, pinia);
  // 重置状态  不能在 createSetupStore 使用
  store.$reset = () => {
    const initialState = state ? state() : {};
    store.$patch(($state) => {
      Object.assign($state, initialState);
    });
  };

  return store;
}
